Completed
Pull Request — master (#91)
by thomas
01:00
created

Wallet.labelAddress   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
nc 1
dl 0
loc 5
rs 9.4285
nop 3
1
var _ = require('lodash');
2
var assert = require('assert');
3
var q = require('q');
4
var async = require('async');
5
var bitcoin = require('bitcoinjs-lib');
6
var bitcoinMessage = require('bitcoinjs-message');
7
var blocktrail = require('./blocktrail');
8
var CryptoJS = require('crypto-js');
9
var Encryption = require('./encryption');
10
var EncryptionMnemonic = require('./encryption_mnemonic');
11
var SizeEstimation = require('./size_estimation');
12
var bip39 = require('bip39');
13
14
var SignMode = {
15
    SIGN: "sign",
16
    DONT_SIGN: "dont_sign"
17
};
18
19
/**
20
 *
21
 * @param sdk                   APIClient       SDK instance used to do requests
22
 * @param identifier            string          identifier of the wallet
23
 * @param walletVersion         string
24
 * @param primaryMnemonic       string          primary mnemonic
25
 * @param encryptedPrimarySeed
26
 * @param encryptedSecret
27
 * @param primaryPublicKeys     string          primary mnemonic
28
 * @param backupPublicKey       string          BIP32 master pubKey M/
29
 * @param blocktrailPublicKeys  array           list of blocktrail pubKeys indexed by keyIndex
30
 * @param keyIndex              int             key index to use
31
 * @param segwit                int             segwit toggle from server
32
 * @param testnet               bool            testnet
33
 * @param checksum              string
34
 * @param upgradeToKeyIndex     int
35
 * @param useNewCashAddr        bool            flag to opt in to bitcoin cash cashaddr's
36
 * @param bypassNewAddressCheck bool            flag to indicate if wallet should/shouldn't derive new address locally to verify api
37
 * @constructor
38
 * @internal
39
 */
40
var Wallet = function(
41
    sdk,
42
    identifier,
43
    walletVersion,
44
    primaryMnemonic,
45
    encryptedPrimarySeed,
46
    encryptedSecret,
47
    primaryPublicKeys,
48
    backupPublicKey,
49
    blocktrailPublicKeys,
50
    keyIndex,
51
    segwit,
52
    testnet,
53
    checksum,
54
    upgradeToKeyIndex,
55
    useNewCashAddr,
56
    bypassNewAddressCheck
57
) {
58
    /* jshint -W071 */
59
    var self = this;
60
61
    self.sdk = sdk;
62
    self.identifier = identifier;
63
    self.walletVersion = walletVersion;
64
    self.locked = true;
65
    self.bypassNewAddressCheck = !!bypassNewAddressCheck;
66
    self.bitcoinCash = self.sdk.bitcoinCash;
67
    self.segwit = !!segwit;
68
    self.useNewCashAddr = !!useNewCashAddr;
69
    assert(!self.segwit || !self.bitcoinCash);
70
71
    self.testnet = testnet;
72
    if (self.bitcoinCash) {
73
        if (self.testnet) {
74
            self.network = bitcoin.networks.bitcoincashtestnet;
75
        } else {
76
            self.network = bitcoin.networks.bitcoincash;
77
        }
78
    } else {
79
        if (self.testnet) {
80
            self.network = bitcoin.networks.testnet;
81
        } else {
82
            self.network = bitcoin.networks.bitcoin;
83
        }
84
    }
85
86
    assert(backupPublicKey instanceof bitcoin.HDNode);
87
    assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; }));
88
    assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; }));
89
90
    // v1
91
    self.primaryMnemonic = primaryMnemonic;
92
93
    // v2 & v3
94
    self.encryptedPrimarySeed = encryptedPrimarySeed;
95
    self.encryptedSecret = encryptedSecret;
96
97
    self.primaryPrivateKey = null;
98
    self.backupPrivateKey = null;
99
100
    self.backupPublicKey = backupPublicKey;
101
    self.blocktrailPublicKeys = blocktrailPublicKeys;
102
    self.primaryPublicKeys = primaryPublicKeys;
103
    self.keyIndex = keyIndex;
104
105
    if (!self.bitcoinCash) {
106
        if (self.segwit) {
107
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
108
            self.changeChain = Wallet.CHAIN_BTC_SEGWIT;
109
        } else {
110
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
111
            self.changeChain = Wallet.CHAIN_BTC_DEFAULT;
112
        }
113
    } else {
114
        self.chain = Wallet.CHAIN_BCC_DEFAULT;
115
        self.changeChain = Wallet.CHAIN_BCC_DEFAULT;
116
    }
117
118
    self.checksum = checksum;
119
    self.upgradeToKeyIndex = upgradeToKeyIndex;
120
121
    self.secret = null;
122
    self.seedHex = null;
123
};
124
125
Wallet.WALLET_VERSION_V1 = 'v1';
126
Wallet.WALLET_VERSION_V2 = 'v2';
127
Wallet.WALLET_VERSION_V3 = 'v3';
128
129
Wallet.WALLET_ENTROPY_BITS = 256;
130
131
Wallet.OP_RETURN = 'opreturn';
132
Wallet.DATA = Wallet.OP_RETURN; // alias
133
134
Wallet.PAY_PROGRESS_START = 0;
135
Wallet.PAY_PROGRESS_COIN_SELECTION = 10;
136
Wallet.PAY_PROGRESS_CHANGE_ADDRESS = 20;
137
Wallet.PAY_PROGRESS_SIGN = 30;
138
Wallet.PAY_PROGRESS_SEND = 40;
139
Wallet.PAY_PROGRESS_DONE = 100;
140
141
Wallet.CHAIN_BTC_DEFAULT = 0;
142
Wallet.CHAIN_BTC_SEGWIT = 2;
143
Wallet.CHAIN_BCC_DEFAULT = 1;
144
145
Wallet.FEE_STRATEGY_FORCE_FEE = blocktrail.FEE_STRATEGY_FORCE_FEE;
146
Wallet.FEE_STRATEGY_BASE_FEE = blocktrail.FEE_STRATEGY_BASE_FEE;
147
Wallet.FEE_STRATEGY_HIGH_PRIORITY = blocktrail.FEE_STRATEGY_HIGH_PRIORITY;
148
Wallet.FEE_STRATEGY_OPTIMAL = blocktrail.FEE_STRATEGY_OPTIMAL;
149
Wallet.FEE_STRATEGY_LOW_PRIORITY = blocktrail.FEE_STRATEGY_LOW_PRIORITY;
150
Wallet.FEE_STRATEGY_MIN_RELAY_FEE = blocktrail.FEE_STRATEGY_MIN_RELAY_FEE;
151
152
Wallet.prototype.isSegwit = function() {
153
    return !!this.segwit;
154
};
155
156
Wallet.prototype.unlock = function(options, cb) {
157
    var self = this;
158
159
    var deferred = q.defer();
160
    deferred.promise.nodeify(cb);
161
162
    // avoid modifying passed options
163
    options = _.merge({}, options);
164
165
    q.fcall(function() {
166
        switch (self.walletVersion) {
167
            case Wallet.WALLET_VERSION_V1:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
168
                return self.unlockV1(options);
169
170
            case Wallet.WALLET_VERSION_V2:
171
                return self.unlockV2(options);
172
173
            case Wallet.WALLET_VERSION_V3:
174
                return self.unlockV3(options);
175
176
            default:
177
                return q.reject(new blocktrail.WalletInitError("Invalid wallet version"));
178
        }
179
    }).then(
180
        function(primaryPrivateKey) {
181
            self.primaryPrivateKey = primaryPrivateKey;
182
183
            // create a checksum of our private key which we'll later use to verify we used the right password
184
            var checksum = self.primaryPrivateKey.getAddress();
185
186
            // check if we've used the right passphrase
187
            if (checksum !== self.checksum) {
188
                throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " +
189
                    "[" + self.checksum + "], most likely due to incorrect password");
190
            }
191
192
            self.locked = false;
193
194
            // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
195
            if (typeof self.upgradeToKeyIndex !== "undefined" && self.upgradeToKeyIndex !== null) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if typeof self.upgradeToKey...radeToKeyIndex !== null is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
196
                return self.upgradeKeyIndex(self.upgradeToKeyIndex);
197
            }
198
        }
199
    ).then(
200
        function(r) {
201
            deferred.resolve(r);
202
        },
203
        function(e) {
204
            deferred.reject(e);
205
        }
206
    );
207
208
    return deferred.promise;
209
};
210
211
Wallet.prototype.unlockV1 = function(options) {
212
    var self = this;
213
214
    options.primaryMnemonic = typeof options.primaryMnemonic !== "undefined" ? options.primaryMnemonic : self.primaryMnemonic;
215
    options.secretMnemonic = typeof options.secretMnemonic !== "undefined" ? options.secretMnemonic : self.secretMnemonic;
216
217
    return self.sdk.resolvePrimaryPrivateKeyFromOptions(options)
218
        .then(function(options) {
219
            self.primarySeed = options.primarySeed;
220
221
            return options.primaryPrivateKey;
222
        });
223
};
224
225
Wallet.prototype.unlockV2 = function(options, cb) {
226
    var self = this;
227
228
    var deferred = q.defer();
229
    deferred.promise.nodeify(cb);
230
231
    deferred.resolve(q.fcall(function() {
232
        /* jshint -W071, -W074 */
233
        options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
234
        options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
235
236
        if (options.secret) {
237
            self.secret = options.secret;
238
        }
239
240
        if (options.primaryPrivateKey) {
241
            throw new blocktrail.WalletDecryptError("specifying primaryPrivateKey has been deprecated");
242
        }
243
244
        if (options.primarySeed) {
245
            self.primarySeed = options.primarySeed;
246
        } else if (options.secret) {
247
            try {
248
                self.primarySeed = new Buffer(
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
249
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
250
                if (!self.primarySeed.length) {
251
                    throw new Error();
252
                }
253
            } catch (e) {
254
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
255
            }
256
257
        } else {
258
            // avoid conflicting options
259
            if (options.passphrase && options.password) {
260
                throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
261
            }
262
            // normalize passphrase/password
263
            options.passphrase = options.passphrase || options.password;
264
265
            try {
266
                self.secret = CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedSecret), options.passphrase).toString(CryptoJS.enc.Utf8);
267
                if (!self.secret.length) {
268
                    throw new Error();
269
                }
270
            } catch (e) {
271
                throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
272
            }
273
            try {
274
                self.primarySeed = new Buffer(
275
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
276
                if (!self.primarySeed.length) {
277
                    throw new Error();
278
                }
279
            } catch (e) {
280
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
281
            }
282
        }
283
284
        return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
285
    }));
286
287
    return deferred.promise;
288
};
289
290
Wallet.prototype.unlockV3 = function(options, cb) {
291
    var self = this;
292
293
    var deferred = q.defer();
294
    deferred.promise.nodeify(cb);
295
296
    deferred.resolve(q.fcall(function() {
297
        return q.when()
298
            .then(function() {
299
                /* jshint -W071, -W074 */
300
                options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
301
                options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
302
303
                if (options.secret) {
304
                    self.secret = options.secret;
305
                }
306
307
                if (options.primaryPrivateKey) {
308
                    throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated");
309
                }
310
311
                if (options.primarySeed) {
312
                    self.primarySeed = options.primarySeed;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
313
                } else if (options.secret) {
314
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
315
                        .then(function(primarySeed) {
316
                            self.primarySeed = primarySeed;
317
                        }, function() {
318
                            throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
319
                        });
320
                } else {
321
                    // avoid conflicting options
322
                    if (options.passphrase && options.password) {
323
                        throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
324
                    }
325
                    // normalize passphrase/password
326
                    options.passphrase = options.passphrase || options.password;
327
                    delete options.password;
328
329
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedSecret, 'base64'), new Buffer(options.passphrase))
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
330
                        .then(function(secret) {
331
                            self.secret = secret;
332
                        }, function() {
333
                            throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
334
                        })
335
                        .then(function() {
336
                            return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
337
                                .then(function(primarySeed) {
338
                                    self.primarySeed = primarySeed;
339
                                }, function() {
340
                                    throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
341
                                });
342
                        });
343
                }
344
            })
345
            .then(function() {
346
                return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
347
            })
348
        ;
349
    }));
350
351
    return deferred.promise;
352
};
353
354
Wallet.prototype.lock = function() {
355
    var self = this;
356
357
    self.secret = null;
358
    self.primarySeed = null;
359
    self.primaryPrivateKey = null;
360
    self.backupPrivateKey = null;
361
362
    self.locked = true;
363
};
364
365
/**
366
 * upgrade wallet to V3 encryption scheme
367
 *
368
 * @param passphrase is required again to reencrypt the data, important that it's the correct password!!!
369
 * @param cb
370
 * @returns {promise}
371
 */
372
Wallet.prototype.upgradeToV3 = function(passphrase, cb) {
373
    var self = this;
374
375
    var deferred = q.defer();
376
    deferred.promise.nodeify(cb);
377
378
    q.when(true)
379
        .then(function() {
380
            if (self.locked) {
381
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade");
382
            }
383
384
            if (self.walletVersion === Wallet.WALLET_VERSION_V3) {
385
                throw new blocktrail.WalletUpgradeError("Wallet is already V3");
386
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
387
                return self._upgradeV2ToV3(passphrase, deferred.notify.bind(deferred));
388
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if self.walletVersion === Wallet.WALLET_VERSION_V1 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
389
                return self._upgradeV1ToV3(passphrase, deferred.notify.bind(deferred));
390
            }
391
        })
392
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
393
394
    return deferred.promise;
395
};
396
397
Wallet.prototype._upgradeV2ToV3 = function(passphrase, notify) {
398
    var self = this;
399
400
    return q.when(true)
401
        .then(function() {
402
            var options = {
403
                storeDataOnServer: true,
404
                passphrase: passphrase,
405
                primarySeed: self.primarySeed,
406
                recoverySecret: false // don't create new recovery secret, V2 already has ones
407
            };
408
409
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
410
                .then(function(options) {
411
                    return self.sdk.updateWallet(self.identifier, {
412
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
413
                        encrypted_secret: options.encryptedSecret.toString('base64'),
414
                        wallet_version: Wallet.WALLET_VERSION_V3
415
                    }).then(function() {
416 View Code Duplication
                        self.secret = options.secret;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
417
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
418
                        self.encryptedSecret = options.encryptedSecret;
419
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
420
421
                        return self;
422
                    });
423
                });
424
        });
425
426
};
427
428
Wallet.prototype._upgradeV1ToV3 = function(passphrase, notify) {
429
    var self = this;
430
431
    return q.when(true)
432
        .then(function() {
433
            var options = {
434
                storeDataOnServer: true,
435
                passphrase: passphrase,
436
                primarySeed: self.primarySeed
437
            };
438
439
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
440
                .then(function(options) {
441
                    // store recoveryEncryptedSecret for printing on backup sheet
442
                    self.recoveryEncryptedSecret = options.recoveryEncryptedSecret;
443
444
                    return self.sdk.updateWallet(self.identifier, {
445
                        primary_mnemonic: '',
446
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
447
                        encrypted_secret: options.encryptedSecret.toString('base64'),
448
                        recovery_secret: options.recoverySecret.toString('hex'),
449
                        wallet_version: Wallet.WALLET_VERSION_V3
450
                    }).then(function() {
451
                        self.secret = options.secret;
452
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
453
                        self.encryptedSecret = options.encryptedSecret;
454
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
455
456
                        return self;
457
                    });
458
                });
459
        });
460
};
461
462
Wallet.prototype.doPasswordChange = function(newPassword) {
463
    var self = this;
464
465
    return q.when(null)
466
        .then(function() {
467
468
            if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
469
                throw new blocktrail.WalletLockedError("Wallet version does not support password change!");
470
            }
471
472
            if (self.locked) {
473
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to change password");
474
            }
475
476
            if (!self.secret) {
477
                throw new blocktrail.WalletLockedError("No secret");
478
            }
479
480
            var newEncryptedSecret;
481
            var newEncrypedWalletSecretMnemonic;
482
            if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
483
                newEncryptedSecret = CryptoJS.AES.encrypt(self.secret, newPassword).toString(CryptoJS.format.OpenSSL);
484
                newEncrypedWalletSecretMnemonic = bip39.entropyToMnemonic(blocktrail.convert(newEncryptedSecret, 'base64', 'hex'));
485
486
            } else {
487
                if (typeof newPassword === "string") {
488
                    newPassword = new Buffer(newPassword);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
489
                } else {
490
                    if (!(newPassword instanceof Buffer)) {
491
                        throw new Error('New password must be provided as a string or a Buffer');
492
                    }
493
                }
494
495
                newEncryptedSecret = Encryption.encrypt(self.secret, newPassword);
496
                newEncrypedWalletSecretMnemonic = EncryptionMnemonic.encode(newEncryptedSecret);
497
498
                // It's a buffer, so convert it back to base64
499
                newEncryptedSecret = newEncryptedSecret.toString('base64');
500
            }
501
502
            return [newEncryptedSecret, newEncrypedWalletSecretMnemonic];
503
        });
504
};
505
506
Wallet.prototype.passwordChange = function(newPassword, cb) {
507
    var self = this;
508
509
    var deferred = q.defer();
510
    deferred.promise.nodeify(cb);
511
512
    q.fcall(function() {
513
        return self.doPasswordChange(newPassword)
514
            .then(function(r) {
515
                var newEncryptedSecret = r[0];
516
                var newEncrypedWalletSecretMnemonic = r[1];
517
518
                return self.sdk.updateWallet(self.identifier, {encrypted_secret: newEncryptedSecret}).then(function() {
519
                    self.encryptedSecret = newEncryptedSecret;
520
521
                    // backupInfo
522
                    return {
523
                        encryptedSecret: newEncrypedWalletSecretMnemonic
524
                    };
525
                });
526
            })
527
            .then(
528
                function(r) {
529
                    deferred.resolve(r);
530
                },
531
                function(e) {
532
                    deferred.reject(e);
533
                }
534
            );
535
    });
536
537
    return deferred.promise;
538
};
539
540
/**
541
 * get address for specified path
542
 *
543
 * @param path
544
 * @returns string
545
 */
546
Wallet.prototype.getAddressByPath = function(path) {
547
    return this.getWalletScriptByPath(path).address;
548
};
549
550
/**
551
 * get redeemscript for specified path
552
 *
553
 * @param path
554
 * @returns {Buffer}
555
 */
556
Wallet.prototype.getRedeemScriptByPath = function(path) {
557
    return this.getWalletScriptByPath(path).redeemScript;
558
};
559
560
/**
561
 * Generate scripts, and address.
562
 * @param path
563
 * @returns {{witnessScript: *, redeemScript: *, scriptPubKey, address: *}}
564
 */
565
Wallet.prototype.getWalletScriptByPath = function(path) {
566
    var self = this;
567
568
    // get derived primary key
569
    var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path);
570
    // get derived blocktrail key
571
    var derivedBlocktrailPublicKey = self.getBlocktrailPublicKey(path);
572
    // derive the backup key
573
    var derivedBackupPublicKey = Wallet.deriveByPath(self.backupPublicKey, path.replace("'", ""), "M");
574
575
    // sort the pubkeys
576
    var pubKeys = Wallet.sortMultiSigKeys([
577
        derivedPrimaryPublicKey.keyPair.getPublicKeyBuffer(),
578
        derivedBackupPublicKey.keyPair.getPublicKeyBuffer(),
579
        derivedBlocktrailPublicKey.keyPair.getPublicKeyBuffer()
580
    ]);
581
582
    var multisig = bitcoin.script.multisig.output.encode(2, pubKeys);
583
    var scriptType = parseInt(path.split("/")[2]);
584
585
    var ws, rs;
586
    if (this.network !== "bitcoincash" && scriptType === Wallet.CHAIN_BTC_SEGWIT) {
587
        ws = multisig;
588
        rs = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(ws));
589
    } else {
590
        ws = null;
591
        rs = multisig;
592
    }
593
594
    var spk = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(rs));
595
    var addr = bitcoin.address.fromOutputScript(spk, this.network, self.useNewCashAddr);
596
597
    return {
598
        witnessScript: ws,
599
        redeemScript: rs,
600
        scriptPubKey: spk,
601
        address: addr
602
    };
603
};
604
605
/**
606
 * get primary public key by path
607
 *  first level of the path is used as keyIndex to find the correct key in the dict
608
 *
609
 * @param path  string
610
 * @returns {bitcoin.HDNode}
611
 */
612
Wallet.prototype.getPrimaryPublicKey = function(path) {
613
    var self = this;
614
615
    path = path.replace("m", "M");
616
617
    var keyIndex = path.split("/")[1].replace("'", "");
618
619
    if (!self.primaryPublicKeys[keyIndex]) {
620
        if (self.primaryPrivateKey) {
621
            self.primaryPublicKeys[keyIndex] = Wallet.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m");
622
        } else {
623
            throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us");
624
        }
625
    }
626
627
    var primaryPublicKey = self.primaryPublicKeys[keyIndex];
628
    return Wallet.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'");
629
};
630
631
/**
632
 * get blocktrail public key by path
633
 *  first level of the path is used as keyIndex to find the correct key in the dict
634
 *
635
 * @param path  string
636
 * @returns {bitcoin.HDNode}
637
 */
638
Wallet.prototype.getBlocktrailPublicKey = function(path) {
639
    var self = this;
640
641
    path = path.replace("m", "M");
642
643
    var keyIndex = path.split("/")[1].replace("'", "");
644
645
    if (!self.blocktrailPublicKeys[keyIndex]) {
646
        throw new blocktrail.KeyPathError("Wallet.getBlocktrailPublicKey keyIndex (" + keyIndex + ") is unknown to us");
647
    }
648
649
    var blocktrailPublicKey = self.blocktrailPublicKeys[keyIndex];
650
651
    return Wallet.deriveByPath(blocktrailPublicKey, path, "M/" + keyIndex + "'");
652
};
653
654
/**
655
 * upgrade wallet to different blocktrail cosign key
656
 *
657
 * @param keyIndex  int
658
 * @param [cb]      function
659
 * @returns {q.Promise}
660
 */
661
Wallet.prototype.upgradeKeyIndex = function(keyIndex, cb) {
662
    var self = this;
663
664
    var deferred = q.defer();
665
    deferred.promise.nodeify(cb);
666
667
    if (self.locked) {
668
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade key index"));
669
        return deferred.promise;
670
    }
671
672
    var primaryPublicKey = self.primaryPrivateKey.deriveHardened(keyIndex).neutered();
673
674
    deferred.resolve(
675
        self.sdk.upgradeKeyIndex(self.identifier, keyIndex, [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"])
676
            .then(function(result) {
677
                self.keyIndex = keyIndex;
678
                _.forEach(result.blocktrail_public_keys, function(publicKey, keyIndex) {
679
                    self.blocktrailPublicKeys[keyIndex] = bitcoin.HDNode.fromBase58(publicKey[0], self.network);
680
                });
681
682
                self.primaryPublicKeys[keyIndex] = primaryPublicKey;
683
684
                return true;
685
            })
686
    );
687
688
    return deferred.promise;
689
};
690
691
/**
692
 * generate a new derived private key and return the new address for it
693
 *
694
 * @param [chainIdx] int
695
 * @param [cb]  function        callback(err, address)
696
 * @returns {q.Promise}
697
 */
698
Wallet.prototype.getNewAddress = function(chainIdx, cb) {
699
    var self = this;
700
701
    // chainIdx is optional
702
    if (typeof chainIdx === "function") {
703
        cb = chainIdx;
704
        chainIdx = null;
705
    }
706
707
    var deferred = q.defer();
708
    deferred.promise.spreadNodeify(cb);
709
710
    // Only enter if it's not an integer
711
    if (chainIdx !== parseInt(chainIdx, 10)) {
712
        // deal with undefined or null, assume defaults
713
        if (typeof chainIdx === "undefined" || chainIdx === null) {
714
            chainIdx = self.chain;
715
        } else {
716
            // was a variable but not integer
717
            deferred.reject(new Error("Invalid chain index"));
718
            return deferred.promise;
719
        }
720
    }
721
722
    deferred.resolve(
723
        self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx)
724
            .then(function(newDerivation) {
725
                var path = newDerivation.path;
726
                var addressFromServer = newDerivation.address;
727
                if (!self.bypassNewAddressCheck) {
728
                    var decodedFromServer;
729
730
                    try {
731
                        // Decode the address the serer gave us
732
                        decodedFromServer = self.decodeAddress(addressFromServer);
733
                    } catch (e) {
734
                        throw new blocktrail.WalletAddressError("Failed to decode address [" + newDerivation.address + "]");
735
                    }
736
737
                    // We need to reproduce this address with the same path,
738
                    // but the server (for BCH cashaddrs) uses base58?
739
                    var verifyAddress = self.getAddressByPath(newDerivation.path);
740
741
                    // If this occasion arises:
742
                    if ("cashAddrPrefix" in self.network && self.useNewCashAddr && decodedFromServer.type === "base58") {
743
                        // Decode our the address we produced for the path
744
                        var decodeOurs;
745
                        try {
746
                            decodeOurs = self.decodeAddress(verifyAddress);
747
                        } catch (e) {
748
                            throw new blocktrail.WalletAddressError("Error while verifying address from server [" + e.message + "]");
749
                        }
750
751
                        // Peek beyond the encoding - the hashes must match at least
752
                        if (decodeOurs.decoded.hash.toString('hex') !== decodedFromServer.decoded.hash.toString('hex')) {
753
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [hash mismatch]");
754
                        }
755
756
                        var matchedP2PKH = decodeOurs.decoded.version === bitcoin.script.types.P2PKH &&
757
                            decodedFromServer.decoded.version === self.network.pubKeyHash;
758
                        var matchedP2SH = decodeOurs.decoded.version === bitcoin.script.types.P2SH &&
759
                            decodedFromServer.decoded.version === self.network.scriptHash;
760
761
                        if (!(matchedP2PKH || matchedP2SH)) {
762
                            throw new blocktrail.WalletAddressError("Failed to verify legacy address [prefix mismatch]");
763
                        }
764
765
                        // We are satisfied that the address is for the same
766
                        // destination, so substitute addressFromServer with our
767
                        // 'reencoded' form.
768
                        addressFromServer = decodeOurs.address;
769
                    }
770
771
                    // debug check
772
                    if (verifyAddress !== addressFromServer) {
773
                        throw new blocktrail.WalletAddressError("Failed to verify address [" + newDerivation.address + "] !== [" + addressFromServer + "]");
774
                    }
775
                }
776
777
                return [addressFromServer, path];
778
            })
779
    );
780
781
    return deferred.promise;
782
};
783
784
/**
785
 * get the balance for the wallet
786
 *
787
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
788
 * @returns {q.Promise}
789 View Code Duplication
 */
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
790
Wallet.prototype.getBalance = function(cb) {
791
    var self = this;
792
793
    var deferred = q.defer();
794
    deferred.promise.spreadNodeify(cb);
795
796
    deferred.resolve(
797
        self.sdk.getWalletBalance(self.identifier)
798
            .then(function(result) {
799
                return [result.confirmed, result.unconfirmed];
800
            })
801
    );
802
803
    return deferred.promise;
804
};
805
806
/**
807
 * get the balance for the wallet
808
 *
809
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
810
 * @returns {q.Promise}
811
 */
812
Wallet.prototype.getInfo = function(cb) {
813
    var self = this;
814
815
    var deferred = q.defer();
816
    deferred.promise.spreadNodeify(cb);
817
818
    deferred.resolve(
819
        self.sdk.getWalletBalance(self.identifier)
820
    );
821
822
    return deferred.promise;
823
};
824
825
/**
826
 * do wallet discovery (slow)
827
 *
828
 * @param [gap] int             gap limit
829
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
830
 * @returns {q.Promise}
831
 */
832
Wallet.prototype.doDiscovery = function(gap, cb) {
833
    var self = this;
834
835
    if (typeof gap === "function") {
836
        cb = gap;
837
        gap = null;
838
    }
839
840
    var deferred = q.defer();
841
    deferred.promise.spreadNodeify(cb);
842
843
    deferred.resolve(
844
        self.sdk.doWalletDiscovery(self.identifier, gap)
845
            .then(function(result) {
846
                return [result.confirmed, result.unconfirmed];
847
            })
848
    );
849
850
    return deferred.promise;
851
};
852
853
/**
854
 *
855
 * @param [force]   bool            ignore warnings (such as non-zero balance)
856
 * @param [cb]      function        callback(err, success)
857
 * @returns {q.Promise}
858
 */
859
Wallet.prototype.deleteWallet = function(force, cb) {
860
    var self = this;
861
862
    if (typeof force === "function") {
863
        cb = force;
864
        force = false;
865
    }
866
867 View Code Duplication
    var deferred = q.defer();
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
868
    deferred.promise.nodeify(cb);
869
870
    if (self.locked) {
871
        deferred.reject(new blocktrail.WalletDeleteError("Wallet needs to be unlocked to delete wallet"));
872
        return deferred.promise;
873
    }
874
875
    var checksum = self.primaryPrivateKey.getAddress();
876
    var privBuf = self.primaryPrivateKey.keyPair.d.toBuffer(32);
877
    var signature = bitcoinMessage.sign(checksum, self.network.messagePrefix, privBuf, true).toString('base64');
878
879
    deferred.resolve(
880
        self.sdk.deleteWallet(self.identifier, checksum, signature, force)
881
            .then(function(result) {
882
                return result.deleted;
883
            })
884
    );
885
886
    return deferred.promise;
887
};
888
889
/**
890
 * create, sign and send a transaction
891
 *
892
 * @param pay                   array       {'address': (int)value}     coins to send
893
 * @param [changeAddress]       bool        change address to use (auto generated if NULL)
894
 * @param [allowZeroConf]       bool        allow zero confirmation unspent outputs to be used in coin selection
895
 * @param [randomizeChangeIdx]  bool        randomize the index of the change output (default TRUE, only disable if you have a good reason to)
896
 * @param [feeStrategy]         string      defaults to Wallet.FEE_STRATEGY_OPTIMAL
897
 * @param [twoFactorToken]      string      2FA token
898
 * @param options
899
 * @param [cb]                  function    callback(err, txHash)
900
 * @returns {q.Promise}
901
 */
902
Wallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) {
903
904
    /* jshint -W071 */
905
    var self = this;
906
907
    if (typeof changeAddress === "function") {
908
        cb = changeAddress;
909
        changeAddress = null;
910
    } else if (typeof allowZeroConf === "function") {
911
        cb = allowZeroConf;
912
        allowZeroConf = false;
913
    } else if (typeof randomizeChangeIdx === "function") {
914
        cb = randomizeChangeIdx;
915
        randomizeChangeIdx = true;
916
    } else if (typeof feeStrategy === "function") {
917
        cb = feeStrategy;
918
        feeStrategy = null;
919
    } else if (typeof twoFactorToken === "function") {
920
        cb = twoFactorToken;
921
        twoFactorToken = null;
922
    } else if (typeof options === "function") {
923
        cb = options;
924
        options = {};
925
    }
926
927
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
928
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
929
    options = options || {};
930
    var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true;
931
932
    var deferred = q.defer();
933
    deferred.promise.nodeify(cb);
934
935
    if (self.locked) {
936
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins"));
937
        return deferred.promise;
938
    }
939
940
    q.nextTick(function() {
941
        deferred.notify(Wallet.PAY_PROGRESS_START);
942
        self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options)
943
            .then(
944
            function(r) { return r; },
945
            function(e) { deferred.reject(e); },
946
            function(progress) {
947
                deferred.notify(progress);
948
            }
949
        )
950
            .spread(
951
            function(tx, utxos) {
952
953
                deferred.notify(Wallet.PAY_PROGRESS_SEND);
954
955
                var data = {
956
                    signed_transaction: tx.toHex(),
957
                    base_transaction: tx.__toBuffer(null, null, false).toString('hex')
958
                };
959
960
                return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost)
961
                    .then(function(result) {
962
                        deferred.notify(Wallet.PAY_PROGRESS_DONE);
963
964
                        if (!result || !result['complete'] || result['complete'] === 'false') {
965
                            deferred.reject(new blocktrail.TransactionSignError("Failed to completely sign transaction"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
966
                        } else {
967
                            return result['txid'];
968
                        }
969
                    });
970
            },
971
            function(e) {
972
                throw e;
973
            }
974
        )
975
            .then(
976
            function(r) { deferred.resolve(r); },
977
            function(e) { deferred.reject(e); }
978
        )
979
        ;
980
    });
981
982
    return deferred.promise;
983
};
984
985
Wallet.prototype.decodeAddress = function(address) {
986
    return Wallet.getAddressAndType(address, this.network, this.useNewCashAddr);
987
};
988
989
function readBech32Address(address, network) {
990
    var addr;
991
    var err;
992
    try {
993
        addr = bitcoin.address.fromBech32(address, network);
994
        err = null;
995
996
    } catch (_err) {
997
        err = _err;
998
    }
999
1000
    if (!err) {
1001
        // Valid bech32 but invalid network immediately alerts
1002
        if (addr.prefix !== network.bech32) {
1003
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
1004
        }
1005
    }
1006
1007
    return [err, addr];
1008
}
1009
1010
function readCashAddress(address, network) {
1011
    var addr;
1012
    var err;
1013
    try {
1014
        addr = bitcoin.address.fromBase32(address);
1015
        err = null;
1016
    } catch (_err) {
1017
        err = _err;
1018
    }
1019
1020
    if (!err) {
1021
        // Valid base58 but invalid network immediately alerts
1022
        if (addr.prefix !== network.cashAddrPrefix) {
1023
            throw new Error(address + ' has an invalid prefix');
1024
        }
1025
    }
1026
1027
    return [err, addr];
1028
}
1029
1030
function readBase58Address(address, network) {
1031
    var addr;
1032
    var err;
1033
    try {
1034
        addr = bitcoin.address.fromBase58Check(address);
1035
        err = null;
1036
    } catch (_err) {
1037
        err = _err;
1038
    }
1039
1040
    if (!err) {
1041
        // Valid base58 but invalid network immediately alerts
1042
        if (addr.version !== network.pubKeyHash && addr.version !== network.scriptHash) {
1043
            throw new blocktrail.InvalidAddressError("Address invalid on this network");
1044
        }
1045
    }
1046
1047
    return [err, addr];
1048
}
1049
1050
Wallet.getAddressAndType = function(address, network, allowCashAddress) {
1051
    var addr;
1052
    var type;
1053
    var err;
1054
1055
    function readAddress(reader, readType) {
1056
        var decoded = reader(address, network);
1057
        if (decoded[0] === null) {
1058
            addr = decoded[1];
1059
            type = readType;
1060
        } else {
1061
            err = decoded[0];
1062
        }
1063
    }
1064
1065
    if (network === bitcoin.networks.bitcoin || network === bitcoin.networks.testnet) {
1066
        readAddress(readBech32Address, "bech32");
1067
    }
1068
1069
    if (!addr && 'cashAddrPrefix' in network && allowCashAddress) {
1070
        readAddress(readCashAddress, "cashaddr");
1071
    }
1072
1073
    if (!addr) {
1074
        readAddress(readBase58Address, "base58");
1075
    }
1076
1077
    if (err) {
1078
        throw new blocktrail.InvalidAddressError(err.message);
1079
    }
1080
1081
    return {
1082
        address: address,
1083
        decoded: addr,
0 ignored issues
show
Bug introduced by
The variable addr seems to not be initialized for all possible execution paths.
Loading history...
1084
        type: type
1085
    };
1086
};
1087
1088
Wallet.convertPayToOutputs = function(pay, network, allowCashAddr) {
1089
    var send = [];
1090
1091
    var readFunc;
1092
1093
    // Deal with two different forms
1094
    if (Array.isArray(pay)) {
1095
        // output[]
1096
        readFunc = function(i, output, obj) {
1097
            if (typeof output !== "object") {
1098
                throw new Error("Invalid transaction output for numerically indexed list [1]");
1099
            }
1100
1101
            var keys = Object.keys(output);
1102
            if (keys.indexOf("scriptPubKey") !== -1 && keys.indexOf("value") !== -1) {
1103
                obj.scriptPubKey = output["scriptPubKey"];
1104
                obj.value = output["value"];
1105
            } else if (keys.indexOf("address") !== -1 && keys.indexOf("value") !== -1) {
1106
                obj.address = output["address"];
1107
                obj.value = output["value"];
1108
            } else if (keys.length === 2 && output.length === 2 && keys[0] === '0' && keys[1] === '1') {
1109
                obj.address = output[0];
1110
                obj.value = output[1];
1111
            } else {
1112
                throw new Error("Invalid transaction output for numerically indexed list [2]");
1113
            }
1114
        };
1115
    } else if (typeof pay === "object") {
1116
        // map[addr]amount
1117
        readFunc = function(address, value, obj) {
1118
            obj.address = address.trim();
1119
            obj.value = value;
1120
            if (obj.address === Wallet.OP_RETURN) {
1121
                var datachunk = Buffer.isBuffer(value) ? value : new Buffer(value, 'utf-8');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1122
                obj.scriptPubKey = bitcoin.script.nullData.output.encode(datachunk).toString('hex');
1123
                obj.value = 0;
1124
                obj.address = null;
1125
            }
1126
        };
1127
    } else {
1128
        throw new Error("Invalid input");
1129
    }
1130
1131
    Object.keys(pay).forEach(function(key) {
1132
        var obj = {};
1133
        readFunc(key, pay[key], obj);
1134
1135
        if (parseInt(obj.value, 10).toString() !== obj.value.toString()) {
1136
            throw new blocktrail.WalletSendError("Values should be in Satoshis");
1137
        }
1138
1139
        // Remove address, replace with scriptPubKey
1140
        if (typeof obj.address === "string") {
1141
            try {
1142
                var addrAndType = Wallet.getAddressAndType(obj.address, network, allowCashAddr);
1143
                obj.scriptPubKey = bitcoin.address.toOutputScript(addrAndType.address, network, allowCashAddr).toString('hex');
1144
                delete obj.address;
1145
            } catch (e) {
1146
                throw new blocktrail.InvalidAddressError("Invalid address [" + obj.address + "] (" + e.message + ")");
1147
            }
1148
        }
1149
1150
        // Extra checks when the output isn't OP_RETURN
1151
        if (obj.scriptPubKey.slice(0, 2) !== "6a") {
1152
            if (!(obj.value = parseInt(obj.value, 10))) {
1153
                throw new blocktrail.WalletSendError("Values should be non zero");
1154
            } else if (obj.value <= blocktrail.DUST) {
1155
                throw new blocktrail.WalletSendError("Values should be more than dust (" + blocktrail.DUST + ")");
1156
            }
1157
        }
1158
1159
        // Value fully checked now
1160
        obj.value = parseInt(obj.value, 10);
1161
1162
        send.push(obj);
1163
    });
1164
1165
    return send;
1166
};
1167
1168
Wallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) {
1169
    /* jshint -W071 */
1170
    var self = this;
1171
1172
    if (typeof changeAddress === "function") {
1173
        cb = changeAddress;
1174
        changeAddress = null;
1175
    } else if (typeof allowZeroConf === "function") {
1176
        cb = allowZeroConf;
1177
        allowZeroConf = false;
1178
    } else if (typeof randomizeChangeIdx === "function") {
1179
        cb = randomizeChangeIdx;
1180
        randomizeChangeIdx = true;
1181
    } else if (typeof feeStrategy === "function") {
1182
        cb = feeStrategy;
1183
        feeStrategy = null;
1184
    } else if (typeof options === "function") {
1185
        cb = options;
1186
        options = {};
1187
    }
1188
1189
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
1190
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1191
    options = options || {};
1192
1193
    var deferred = q.defer();
1194
    deferred.promise.spreadNodeify(cb);
1195
1196
    q.nextTick(function() {
1197
        var send;
1198
        try {
1199
            send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1200
        } catch (e) {
1201
            deferred.reject(e);
1202
            return deferred.promise;
1203
        }
1204
1205
        if (!send.length) {
1206
            deferred.reject(new blocktrail.WalletSendError("Need at least one recipient"));
1207
            return deferred.promise;
1208
        }
1209
1210
        deferred.notify(Wallet.PAY_PROGRESS_COIN_SELECTION);
1211
1212
        deferred.resolve(
1213
            self.coinSelection(send, true, allowZeroConf, feeStrategy, options)
1214
            /**
1215
             *
1216
             * @param {Object[]} utxos
1217
             * @param fee
1218
             * @param change
1219
             * @param randomizeChangeIdx
0 ignored issues
show
Documentation introduced by
The parameter randomizeChangeIdx does not exist. Did you maybe forget to remove this comment?
Loading history...
1220
             * @returns {*}
1221
             */
1222
                .spread(function(utxos, fee, change) {
1223
                    var tx, txb, outputs = [];
1224
1225
                    var deferred = q.defer();
1226
1227
                    async.waterfall([
1228
                        /**
1229
                         * prepare
1230
                         *
1231
                         * @param cb
1232
                         */
1233
                        function(cb) {
1234
                            var inputsTotal = utxos.map(function(utxo) {
1235
                                return utxo['value'];
1236
                            }).reduce(function(a, b) {
1237
                                return a + b;
1238
                            });
1239
                            var outputsTotal = send.map(function(output) {
1240
                                return output.value;
1241
                            }).reduce(function(a, b) {
1242
                                return a + b;
1243
                            });
1244
                            var estimatedChange = inputsTotal - outputsTotal - fee;
1245
1246
                            if (estimatedChange > blocktrail.DUST * 2 && estimatedChange !== change) {
1247
                                return cb(new blocktrail.WalletFeeError("the amount of change (" + change + ") " +
1248
                                    "suggested by the coin selection seems incorrect (" + estimatedChange + ")"));
1249
                            }
1250
1251
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1252
                        },
1253
                        /**
1254
                         * init transaction builder
1255
                         *
1256
                         * @param cb
1257
                         */
1258
                        function(cb) {
1259
                            txb = new bitcoin.TransactionBuilder(self.network);
1260
                            if (self.bitcoinCash) {
1261
                                txb.enableBitcoinCash();
1262
                            }
1263
1264
                            cb();
1265
                        },
1266
                        /**
1267
                         * add UTXOs as inputs
1268
                         *
1269
                         * @param cb
1270
                         */
1271
                        function(cb) {
1272
                            var i;
1273
1274
                            for (i = 0; i < utxos.length; i++) {
1275
                                txb.addInput(utxos[i]['hash'], utxos[i]['idx']);
1276
                            }
1277
1278
                            cb();
1279
                        },
1280
                        /**
1281
                         * build desired outputs
1282
                         *
1283
                         * @param cb
1284
                         */
1285
                        function(cb) {
1286
                            send.forEach(function(_send) {
1287
                                if (_send.address) {
1288
                                    outputs.push({address: _send.address, value: _send.value});
1289
                                } else if (_send.scriptPubKey) {
1290
                                    outputs.push({scriptPubKey: new Buffer(_send.scriptPubKey, 'hex'), value: _send.value});
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1291
                                } else {
1292
                                    throw new Error("Invalid send");
1293
                                }
1294
                            });
1295
                            cb();
1296
                        },
1297
                        /**
1298
                         * get change address if required
1299
                         *
1300
                         * @param cb
1301
                         */
1302
                        function(cb) {
1303
                            if (change > 0) {
1304
                                if (change <= blocktrail.DUST) {
1305
                                    change = 0; // don't do a change output if it would be a dust output
1306
1307
                                } else {
1308
                                    if (!changeAddress) {
1309
                                        deferred.notify(Wallet.PAY_PROGRESS_CHANGE_ADDRESS);
1310
1311
                                        return self.getNewAddress(self.changeChain, function(err, address) {
1312
                                            if (err) {
1313
                                                return cb(err);
1314
                                            }
1315
                                            changeAddress = address;
1316
                                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1317
                                        });
1318
                                    }
1319
                                }
1320
                            }
1321
1322
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1323
                        },
1324
                        /**
1325
                         * add change to outputs
1326
                         *
1327
                         * @param cb
1328
                         */
1329
                        function(cb) {
1330
                            if (change > 0) {
1331
                                if (randomizeChangeIdx) {
1332
                                    outputs.splice(_.random(0, outputs.length), 0, {
1333
                                        address: changeAddress,
1334
                                        value: change
1335
                                    });
1336
                                } else {
1337
                                    outputs.push({address: changeAddress, value: change});
1338
                                }
1339
                            }
1340
1341
                            cb();
1342
                        },
1343
                        /**
1344
                         * add outputs to txb
1345
                         *
1346
                         * @param cb
1347
                         */
1348
                        function(cb) {
1349
                            outputs.forEach(function(outputInfo) {
1350
                                txb.addOutput(outputInfo.scriptPubKey || outputInfo.address, outputInfo.value);
1351
                            });
1352
1353
                            cb();
1354
                        },
1355
                        /**
1356
                         * sign
1357
                         *
1358
                         * @param cb
1359
                         */
1360
                        function(cb) {
1361
                            var i, privKey, path, redeemScript, witnessScript;
1362
1363
                            deferred.notify(Wallet.PAY_PROGRESS_SIGN);
1364
1365
                            for (i = 0; i < utxos.length; i++) {
1366
                                var mode = SignMode.SIGN;
1367
                                if (utxos[i].sign_mode) {
1368
                                    mode = utxos[i].sign_mode;
1369
                                }
1370
1371
                                redeemScript = null;
0 ignored issues
show
Unused Code introduced by
The assignment to redeemScript seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1372
                                witnessScript = null;
1373
                                if (mode === SignMode.SIGN) {
1374
                                    path = utxos[i]['path'].replace("M", "m");
1375
1376
                                    // todo: regenerate scripts for path and compare for utxo (paranoid mode)
1377
                                    if (self.primaryPrivateKey) {
1378
                                        privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m").keyPair;
1379
                                    } else if (self.backupPrivateKey) {
1380
                                        privKey = Wallet.deriveByPath(self.backupPrivateKey, path.replace(/^m\/(\d+)\'/, 'm/$1'), "m").keyPair;
1381
                                    } else {
1382
                                        throw new Error("No master privateKey present");
1383
                                    }
1384
1385
                                    redeemScript = new Buffer(utxos[i]['redeem_script'], 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1386
                                    if (typeof utxos[i]['witness_script'] === 'string') {
1387
                                        witnessScript = new Buffer(utxos[i]['witness_script'], 'hex');
1388
                                    }
1389
1390
                                    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
1391
                                    if (self.bitcoinCash) {
1392
                                        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
1393
                                    }
1394
1395
                                    txb.sign(i, privKey, redeemScript, sigHash, utxos[i].value, witnessScript);
1396
                                }
1397
                            }
1398
1399
                            tx = txb.buildIncomplete();
1400
1401
                            cb();
1402
                        },
1403
                        /**
1404
                         * estimate fee to verify that the API is not providing us wrong data
1405
                         *
1406
                         * @param cb
1407
                         */
1408
                        function(cb) {
1409
                            var estimatedFee = Wallet.estimateVsizeFee(tx, utxos);
1410
1411
                            if (self.sdk.feeSanityCheck) {
1412
                                switch (feeStrategy) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1413
                                    case Wallet.FEE_STRATEGY_BASE_FEE:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1414
                                        if (Math.abs(estimatedFee - fee) > blocktrail.BASE_FEE) {
1415
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1416
                                                "seems incorrect (" + estimatedFee + ") for FEE_STRATEGY_BASE_FEE"));
1417
                                        }
1418
                                    break;
1419
1420
                                    case Wallet.FEE_STRATEGY_HIGH_PRIORITY:
1421
                                    case Wallet.FEE_STRATEGY_OPTIMAL:
1422
                                    case Wallet.FEE_STRATEGY_LOW_PRIORITY:
1423
                                        if (fee > estimatedFee * self.feeSanityCheckBaseFeeMultiplier) {
1424
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1425
                                                "seems awefully high (" + estimatedFee + ") for FEE_STRATEGY_OPTIMAL"));
1426
                                        }
1427
                                    break;
1428
                                }
1429
                            }
1430
1431
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1432
                        }
1433
                    ], function(err) {
1434
                        if (err) {
1435
                            deferred.reject(new blocktrail.WalletSendError(err));
1436
                            return;
1437
                        }
1438
1439
                        deferred.resolve([tx, utxos]);
1440
                    });
1441
1442
                    return deferred.promise;
1443
                }
1444
            )
1445
        );
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1446
    });
1447
1448
    return deferred.promise;
1449
};
1450
1451
1452
/**
1453
 * use the API to get the best inputs to use based on the outputs
1454
 *
1455
 * @param pay               array       {'address': (int)value}     coins to send
1456
 * @param [lockUTXO]        bool        lock UTXOs for a few seconds to allow for transaction to be created
1457
 * @param [allowZeroConf]   bool        allow zero confirmation unspent outputs to be used in coin selection
1458
 * @param [feeStrategy]     string      defaults to FEE_STRATEGY_OPTIMAL
1459
 * @param [options]         object
1460
 * @param [cb]              function    callback(err, utxos, fee, change)
1461
 * @returns {q.Promise}
1462
 */
1463
Wallet.prototype.coinSelection = function(pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1464
    var self = this;
1465
1466
    if (typeof lockUTXO === "function") {
1467
        cb = lockUTXO;
1468
        lockUTXO = true;
1469
    } else if (typeof allowZeroConf === "function") {
1470
        cb = allowZeroConf;
1471
        allowZeroConf = false;
1472
    } else if (typeof feeStrategy === "function") {
1473
        cb = feeStrategy;
1474
        feeStrategy = null;
1475
    } else if (typeof options === "function") {
1476
        cb = options;
1477
        options = {};
1478
    }
1479
1480
    lockUTXO = typeof lockUTXO !== "undefined" ? lockUTXO : true;
1481
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1482
    options = options || {};
1483
1484
    var send;
1485
    try {
1486
        send = Wallet.convertPayToOutputs(pay, self.network, self.useNewCashAddr);
1487
    } catch (e) {
1488
        var deferred = q.defer();
1489
        deferred.promise.nodeify(cb);
1490
        deferred.reject(e);
1491
        return deferred.promise;
1492
    }
1493
1494
    return self.sdk.coinSelection(self.identifier, send, lockUTXO, allowZeroConf, feeStrategy, options, cb);
1495
};
1496
1497
/**
1498
 * send the transaction using the API
1499
 *
1500
 * @param txHex             string      partially signed transaction as hex string
1501
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1502
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1503
 * @param [twoFactorToken]  string      2FA token
1504
 * @param prioboost         bool
1505
 * @param [cb]              function    callback(err, txHash)
1506
 * @returns {q.Promise}
1507
 */
1508
Wallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1509
    var self = this;
1510
1511
    if (typeof twoFactorToken === "function") {
1512
        cb = twoFactorToken;
1513
        twoFactorToken = null;
1514
        prioboost = false;
1515
    } else if (typeof prioboost === "function") {
1516
        cb = twoFactorToken;
1517
        prioboost = false;
1518
    }
1519
1520
    var deferred = q.defer();
1521
    deferred.promise.nodeify(cb);
1522
1523
    self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost)
1524
        .then(
1525
            function(result) {
1526
                deferred.resolve(result);
1527
            },
1528
            function(e) {
1529
                if (e.requires_2fa) {
1530
                    deferred.reject(new blocktrail.WalletMissing2FAError());
1531
                } else if (e.message.match(/Invalid two_factor_token/)) {
1532
                    deferred.reject(new blocktrail.WalletInvalid2FAError());
1533
                } else {
1534
                    deferred.reject(e);
1535
                }
1536
            }
1537
        )
1538
    ;
1539
1540
    return deferred.promise;
1541
};
1542
1543
/**
1544
 * setup a webhook for this wallet
1545
 *
1546
 * @param url           string      URL to receive webhook events
1547
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1548
 * @param [cb]          function    callback(err, webhook)
1549
 * @returns {q.Promise}
1550
 */
1551
Wallet.prototype.setupWebhook = function(url, identifier, cb) {
1552
    var self = this;
1553
1554
    if (typeof identifier === "function") {
1555
        cb = identifier;
1556
        identifier = null;
1557
    }
1558
1559
    identifier = identifier || ('WALLET-' + self.identifier);
1560
1561
    return self.sdk.setupWalletWebhook(self.identifier, identifier, url, cb);
1562
};
1563
1564
/**
1565
 * delete a webhook that was created for this wallet
1566
 *
1567
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1568
 * @param [cb]          function    callback(err, success)
1569
 * @returns {q.Promise}
1570
 */
1571
Wallet.prototype.deleteWebhook = function(identifier, cb) {
1572
    var self = this;
1573
1574
    if (typeof identifier === "function") {
1575
        cb = identifier;
1576
        identifier = null;
1577
    }
1578
1579
    identifier = identifier || ('WALLET-' + self.identifier);
1580
1581
    return self.sdk.deleteWalletWebhook(self.identifier, identifier, cb);
1582
};
1583
1584
/**
1585
 * get all transactions for the wallet (paginated)
1586
 *
1587
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1588
 * @param [cb]      function    callback(err, transactions)
1589
 * @returns {q.Promise}
1590
 */
1591
Wallet.prototype.transactions = function(params, cb) {
1592
    var self = this;
1593
1594
    return self.sdk.walletTransactions(self.identifier, params, cb);
1595
};
1596
1597
Wallet.prototype.maxSpendable = function(allowZeroConf, feeStrategy, options, cb) {
1598
    var self = this;
1599
1600
    if (typeof allowZeroConf === "function") {
1601
        cb = allowZeroConf;
1602
        allowZeroConf = false;
1603
    } else if (typeof feeStrategy === "function") {
1604
        cb = feeStrategy;
1605
        feeStrategy = null;
1606
    } else if (typeof options === "function") {
1607
        cb = options;
1608
        options = {};
1609
    }
1610
1611
    if (typeof allowZeroConf === "object") {
1612
        options = allowZeroConf;
1613
        allowZeroConf = false;
1614
    } else if (typeof feeStrategy === "object") {
1615
        options = feeStrategy;
1616
        feeStrategy = null;
1617
    }
1618
1619
    options = options || {};
1620
1621
    if (typeof options.allowZeroConf !== "undefined") {
1622
        allowZeroConf = options.allowZeroConf;
1623
    }
1624
    if (typeof options.feeStrategy !== "undefined") {
1625
        feeStrategy = options.feeStrategy;
1626
    }
1627
1628
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1629
1630
    return self.sdk.walletMaxSpendable(self.identifier, allowZeroConf, feeStrategy, options, cb);
1631
};
1632
1633
/**
1634
 * get all addresses for the wallet (paginated)
1635
 *
1636
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1637
 * @param [cb]      function    callback(err, addresses)
1638
 * @returns {q.Promise}
1639
 */
1640
Wallet.prototype.addresses = function(params, cb) {
1641
    var self = this;
1642
1643
    return self.sdk.walletAddresses(self.identifier, params, cb);
1644
};
1645
1646
/**
1647
 * @param address   string      the address to label
1648
 * @param label     string      the label
1649
 * @param [cb]      function    callback(err, res)
1650
 * @returns {q.Promise}
1651
 */
1652
Wallet.prototype.labelAddress = function(address, label, cb) {
1653
    var self = this;
1654
1655
    return self.sdk.labelWalletAddress(self.identifier, address, label, cb);
1656
};
1657
1658
/**
1659
 * get all UTXOs for the wallet (paginated)
1660
 *
1661
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1662
 * @param [cb]      function    callback(err, addresses)
1663
 * @returns {q.Promise}
1664
 */
1665
Wallet.prototype.utxos = function(params, cb) {
1666
    var self = this;
1667
1668
    return self.sdk.walletUTXOs(self.identifier, params, cb);
1669
};
1670
1671
Wallet.prototype.unspentOutputs = Wallet.prototype.utxos;
1672
1673
/**
1674
 * sort list of pubkeys to be used in a multisig redeemscript
1675
 *  sorted in lexicographical order on the hex of the pubkey
1676
 *
1677
 * @param pubKeys   {bitcoin.HDNode[]}
1678
 * @returns string[]
1679
 */
1680
Wallet.sortMultiSigKeys = function(pubKeys) {
1681
    pubKeys.sort(function(key1, key2) {
1682
        return key1.toString('hex').localeCompare(key2.toString('hex'));
1683
    });
1684
1685
    return pubKeys;
1686
};
1687
1688
/**
1689
 * determine how much fee is required based on the inputs and outputs
1690
 *  this is an estimation, not a proper 100% correct calculation
1691
 *
1692
 * @todo: mark deprecated in favor of estimations where UTXOS are known
1693
 * @param {bitcoin.Transaction} tx
1694
 * @param {int} feePerKb when not null use this feePerKb, otherwise use BASE_FEE legacy calculation
1695
 * @returns {number}
1696
 */
1697
Wallet.estimateIncompleteTxFee = function(tx, feePerKb) {
1698
    var size = Wallet.estimateIncompleteTxSize(tx);
1699
    var sizeKB = size / 1000;
1700
    var sizeKBCeil = Math.ceil(size / 1000);
1701
1702
    if (feePerKb) {
1703
        return parseInt(sizeKB * feePerKb, 10);
1704
    } else {
1705
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1706
    }
1707
};
1708
1709
/**
1710
 * Takes tx and utxos, computing their estimated vsize,
1711
 * and uses feePerKb (or BASEFEE as default) to estimate
1712
 * the number of satoshis in fee.
1713
 *
1714
 * @param {bitcoin.Transaction} tx
1715
 * @param {Array} utxos
1716
 * @param feePerKb
1717
 * @returns {Number}
1718
 */
1719
Wallet.estimateVsizeFee = function(tx, utxos, feePerKb) {
1720
    var vsize = SizeEstimation.estimateTxVsize(tx, utxos);
1721
    var sizeKB = vsize / 1000;
1722
    var sizeKBCeil = Math.ceil(vsize / 1000);
1723
1724
    if (feePerKb) {
1725
        return parseInt(sizeKB * feePerKb, 10);
1726
    } else {
1727
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1728
    }
1729
};
1730
1731
/**
1732
 * determine how much fee is required based on the inputs and outputs
1733
 *  this is an estimation, not a proper 100% correct calculation
1734
 *
1735
 * @param {bitcoin.Transaction} tx
1736
 * @returns {number}
1737
 */
1738
Wallet.estimateIncompleteTxSize = function(tx) {
1739
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1740
1741
    size += tx.outs.length * 34;
1742
1743
    tx.ins.forEach(function(txin) {
1744
        var scriptSig = txin.script,
1745
            scriptType = bitcoin.script.classifyInput(scriptSig);
1746
1747
        var multiSig = [2, 3]; // tmp hardcoded
1748
1749
        // Re-classify if P2SH
1750
        if (!multiSig && scriptType === 'scripthash') {
1751
            var sigChunks = bitcoin.script.decompile(scriptSig);
1752
            var redeemScript = sigChunks.slice(-1)[0];
1753
            scriptSig = bitcoin.script.compile(sigChunks.slice(0, -1));
1754
            scriptType = bitcoin.script.classifyInput(scriptSig);
1755
1756
            if (bitcoin.script.classifyOutput(redeemScript) !== scriptType) {
1757
                throw new blocktrail.TransactionInputError('Non-matching scriptSig and scriptPubKey in input');
1758
            }
1759
1760
            // figure out M of N for multisig (code from internal usage of bitcoinjs)
1761
            if (scriptType === 'multisig') {
1762
                var rsChunks = bitcoin.script.decompile(redeemScript);
1763
                var mOp = rsChunks[0];
1764
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1765
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1766
                }
1767
1768
                var nOp = rsChunks[redeemScript.chunks.length - 2];
1769
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1770
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1771
                }
1772
1773
                var m = mOp - (bitcoin.opcodes.OP_1 - 1);
1774
                var n = nOp - (bitcoin.opcodes.OP_1 - 1);
1775
                if (n < m) {
1776
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1777
                }
1778
1779
                multiSig = [m, n];
1780
            }
1781
        }
1782
1783
        if (multiSig) {
1784
            size += (
1785
                32 + // txhash
1786
                4 + // idx
1787
                3 + // scriptVarInt[>=253]
1788
                1 + // OP_0
1789
                ((1 + 72) * multiSig[0]) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1790
                (2 + 105) + // OP_PUSHDATA[>=75] + script
1791
                4 // sequence
1792
            );
1793
1794
        } else {
1795
            size += 32 + // txhash
1796
                4 + // idx
1797
                73 + // sig
1798
                34 + // script
1799
                4; // sequence
1800
        }
1801
    });
1802
1803
    return size;
1804
};
1805
1806
/**
1807
 * determine how much fee is required based on the amount of inputs and outputs
1808
 *  this is an estimation, not a proper 100% correct calculation
1809
 *  this asumes all inputs are 2of3 multisig
1810
 *
1811
 * @todo: mark deprecated in favor of situations where UTXOS are known
1812
 * @param txinCnt       {number}
1813
 * @param txoutCnt      {number}
1814
 * @returns {number}
1815
 */
1816
Wallet.estimateFee = function(txinCnt, txoutCnt) {
1817
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1818
1819
    size += txoutCnt * 34;
1820
1821
    size += (
1822
            32 + // txhash
1823
            4 + // idx
1824
            3 + // scriptVarInt[>=253]
1825
            1 + // OP_0
1826
            ((1 + 72) * 2) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1827
            (2 + 105) + // OP_PUSHDATA[>=75] + script
1828
            4 // sequence
1829
        ) * txinCnt;
1830
1831
    var sizeKB = Math.ceil(size / 1000);
1832
1833
    return sizeKB * blocktrail.BASE_FEE;
1834
};
1835
1836
/**
1837
 * create derived key from parent key by path
1838
 *
1839
 * @param hdKey     {bitcoin.HDNode}
1840
 * @param path      string
1841
 * @param keyPath   string
1842
 * @returns {bitcoin.HDNode}
1843
 */
1844
Wallet.deriveByPath = function(hdKey, path, keyPath) {
1845
    keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M");
1846
1847
    if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") {
1848
        throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")");
1849
    }
1850
1851
    if (path[0] === "m" && keyPath[0] === "M") {
1852
        throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")");
1853
    }
1854
1855
    // if the desired path is public while the input is private
1856
    var toPublic = path[0] === "M" && keyPath[0] === "m";
1857
    if (toPublic) {
1858
        // derive the private path, convert to public when returning
1859
        path[0] = "m";
1860
    }
1861
1862
    // keyPath should be the parent parent of path
1863
    if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) {
1864
        throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")");
1865
    }
1866
1867
    // remove the part of the path we already have
1868
    path = path.substr(keyPath.length);
1869
1870
    // iterate over the chunks and derive
1871
    var newKey = hdKey;
1872
    path.replace(/^\//, "").split("/").forEach(function(chunk) {
1873
        if (!chunk) {
1874
            return;
1875
        }
1876
1877
        if (chunk.indexOf("'") !== -1) {
1878
            chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT;
1879
        }
1880
1881
        newKey = newKey.derive(parseInt(chunk, 10));
1882
    });
1883
1884
    if (toPublic) {
1885
        return newKey.neutered();
1886
    } else {
1887
        return newKey;
1888
    }
1889
};
1890
1891
module.exports = Wallet;
1892